/* Copyright (c) 2021, LiWangQian<liwangqian@huawei.com> All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice, this list of
 *    conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice, this list
 *    of conditions and the following disclaimer in the documentation and/or other materials
 *    provided with the distribution.
 *
 * 3. Neither the name of the copyright holder nor the names of its contributors may be used
 *    to endorse or promote products derived from this software without specific prior written
 *    permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
#pragma once

#include <string_view>
#include <libcli/builder/element_inserter.h>
#include <libcli/element.h>

namespace libcli {

/**
 * display { power-usage | cpu-usage | memory-usage } [ brief | verbose ]
 * display cpu-usage information [ cpuid <id> ]
 * 
 */

class command;
class command_builder;

// todo: BuilderProxy ==> ElementInserter
template <typename BuilderProxy, typename ElementInserter>
struct builder_basic_proxy : protected ElementInserter
{
    using ElementInserter::ElementInserter;
    BuilderProxy &keyword(std::string_view name, std::string_view desc);
    BuilderProxy &parameter(std::string_view name, std::string_view desc);
};

template <typename PrevProxy, typename ElementInserter>
struct builder_proxy;

template <typename PrevProxy>
struct builder_proxy<PrevProxy, child_inserter>
    : protected child_inserter
{
public:
    using this_type = builder_proxy<PrevProxy, child_inserter>;
    using base_type = child_inserter;

    builder_proxy(command_builder &builder, PrevProxy &)
        : base_type{builder, nullptr}
        , builder_{builder}
    {}

    this_type &keyword(std::string_view name, std::string_view desc)
    {
        insert(builder_, new key_element{name.data(), desc.data()});
        return *this;
    }

    this_type &parameter(std::string_view name, std::string_view desc)
    {
        insert(builder_, new parameter_element<simple_element>{name.data(), desc.data()});
        return *this;
    }

    auto begin_group()
    {
        return builder_proxy<this_type, brother_inserter>{builder_, *this};
    }
    auto begin_optional()
    {
        return builder_proxy<this_type, optional_inserter>{builder_, *this};
    }

protected:
    command_builder &builder_;
};

template <typename PrevProxy>
struct builder_proxy<PrevProxy, brother_inserter>
    : protected brother_inserter
{
public:
    using this_type = builder_proxy<PrevProxy, brother_inserter>;
    using base_type = brother_inserter;

    builder_proxy(command_builder &builder, PrevProxy &prev_proxy)
        : base_type{builder, nullptr}
        , builder_{builder}
        , prev_proxy_{prev_proxy}
    {}

    this_type &keyword(std::string_view name, std::string_view desc)
    {
        insert(builder_, new key_element{name.data(), desc.data()});
        return *this;
    }

    this_type &parameter(std::string_view name, std::string_view desc)
    {
        insert(builder_, new parameter_element<simple_element>{name.data(), desc.data()});
        return *this;
    }

    auto begin_group()
    {
        return builder_proxy<this_type, brother_inserter>{builder_, *this};
    }

    auto begin_optional()
    {
        return builder_proxy<this_type, optional_inserter>{builder_, *this};
    }

    PrevProxy &end_group()
    {
        return prev_proxy_;
    }

protected:
    command_builder &builder_;
    PrevProxy &prev_proxy_;
};

template <typename PrevProxy>
struct builder_proxy<PrevProxy, optional_inserter>
    : protected optional_inserter
{
public:
    using this_type = builder_proxy<PrevProxy, optional_inserter>;
    using base_type = optional_inserter;

    builder_proxy(command_builder &builder, PrevProxy &prev_proxy)
        : base_type{builder, nullptr}
        , builder_{builder}
        , prev_proxy_{prev_proxy}
    {}

    this_type &keyword(std::string_view name, std::string_view desc)
    {
        insert(builder_, new key_element{name.data(), desc.data()});
        return *this;
    }

    this_type &parameter(std::string_view name, std::string_view desc)
    {
        insert(builder_, new parameter_element<simple_element>{name.data(), desc.data()});
        return *this;
    }

    auto begin_group()
    {
        return builder_proxy<this_type, brother_inserter>{builder_, *this};
    }

    auto begin_optional()
    {
        return builder_proxy<this_type, optional_inserter>{builder_, *this};
    }

    PrevProxy &end_optional()
    {
        return prev_proxy_;
    }

protected:
    command_builder &builder_;
    PrevProxy &prev_proxy_;
};

struct command_builder {
    builder_proxy<command_builder, child_inserter> new_command();
    command *get_command();

protected:
    void insert_brother(element *elem);
    void insert_child(element *elem);

    template <typename PrevBuilder, typename Inserter> friend class builder_proxy;

private:
    command *command_{nullptr};
};

} // namespace libcli

